﻿// Basic single-point perspective 3-D projection engine, handling sprites with basic physics
// Usage: Attach a movieclip that's registered with this class as your 3-D container

import com.spikything.gfx3d.*
import com.spikything.utils.SpikyTools

class com.spikything.gfx3d.ProjectionContainer extends MovieClip {

	//var host 			:MovieClip; 		// The view's talkback target
	//var soundManager 	:MovieClip; 		// Sound container for view (if any)

	var viewDist		:Number;			// Camera focal-length/view-angle
	var maxDist			:Number;			// Maximum distance a sprite is visible
	var minDist			:Number;			// Minimum distance a sprite is visible
	var fadeFactor		:Number;			// Minimum focal range divisor (closeup fading)
	var minAlpha		:Number;			// Minimum closeup alpha before invisible
	var depthQueuing	:Boolean;			// Do we fade sprites into background?
	var depthQueueR		:Number;			// Background fade RED component (0-1)
	var depthQueueG		:Number;			// Background fade GREEN component (0-1)
	var depthQueueB		:Number;			// Background fade BLUE component (0-1)
	var depthQueueFactor:Number;			// Light intensity gradient
	var maxLuminosity	:Number;			// Light intensity limit
	var rounding		:Number;			// Camera position granularity

	var mouseSwayX 		:Number;
	var mouseSwayY 		:Number;

	var x				:Number;			// Camera X pos
	var y				:Number;			// Camera Y pos
	var z				:Number;			// Camera Z pos
	var vx				:Number;			// Camera X speed
	var vy				:Number;			// Camera Y speed
	var vz				:Number;			// Camera Z speed
	var tx				:Number;			// Camera target X
	var ty				:Number;			// Camera target Y
	var tz				:Number;			// Camera target Z

	var maxDepth 		:Number = 65535; 	// Highest possible Z-depth
	var theDepth		:Number = maxDepth;	// Depth we start attaching at
	var zDepthScale		:Number = 100; 		// Default Z-depth/Z-pos factor
	var itemCount		:Number = 0; 		// Track the number of sprites
	var gridSpacing		:Number	= 20;		// Test grid spacing
	var gridSize		:Number	= 20;		// Test grid size
	var gridWidth		:Number	= 300;		// Test grid width

	var damp			:Number;			// Physics seek damping
	var friction		:Number;			// Physics air friction
	var gravity			:Number;			// Physics global gravity
	var elastic			:Number;			// Physics coefficient of elasticity

	var isEnabled		:Boolean;			// Simulation active/visible
	var isPaused 		:Boolean;			// Simulation paused
	var testing			:Boolean;			// Manual key controls enabled
	var mouseControlled	:Boolean;			// Mouse controls enabled
	var ready			:Boolean;			// Controls/interactivity enabled

	// CONSTRUCTOR

	function ProjectionContainer () {}

	// EVENTS

	public function onLoad ():Void {init()}

	// STATICS

	public static function create (_base:MovieClip, _initObj:Object):MovieClip {
		return SpikyTools.createClass(_base,ProjectionContainer,_initObj);
	}

	// INIT

	private function init ():Void {
		disable();
		//host = _parent;
		//if (!soundManager) soundManager = host.soundManager;
		setCamProps(100,500,5,5,85,0,0);
		setCamPos(0,0,0);
		setCamTarget(0,0,0);
		setCamMotion(0,0,0);
		setDepthQueueColours(0,0,0,1);
		setPhysicsProps(9,.6,3,.3);
		onEnterFrame();
	}

	// PUBLICS

	public function renderTestGrid ():Void {
		for (var i=0; i<gridSize; i++) addSprite("dot", gridWidth,	0,		gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", -gridWidth,	0,		gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", gridWidth,	-gridWidth,	gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", -gridWidth,	-gridWidth,	gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", gridWidth,	gridWidth,	gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", -gridWidth,	gridWidth,	gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", 0,		gridWidth,	gridSpacing*i);
		for (var i=0; i<gridSize; i++) addSprite("dot", 0,		-gridWidth,	gridSpacing*i);
		addSprite("crosshair",0,0,100);
	}

	// Adjust camera settings (view distance, max distance, fade factor, min alpha)
	public function setCamProps (vd:Number, maxd:Number, ff:Number, ma:Number, mind:Number, swayX:Number, swayY:Number):Void {
		viewDist = vd;
		maxDist = maxd;
		zDepthScale = Math.floor(maxDepth/maxDist);
		fadeFactor = ff;
		minAlpha = ma;
		minDist = mind;
		if (swayX!=undefined) mouseSwayX = swayX;
		if (swayY!=undefined) mouseSwayY = swayY;
	}

	public function setCamMotion (mx:Number, my:Number, mz:Number):Void {
		vx = mx;
		vy = my;
		vz = mz;
	}

	public function setCamTarget (mx:Number, my:Number, mz:Number):Void {
		tx = mx;
		ty = my;
		tz = mz;
	}

	public function setCamPos (mx:Number, my:Number, mz:Number):Void {
		x = mx;
		y = my;
		z = mz;
	}

	public function setDepthQueueColours (r:Number, g:Number, b:Number, df:Number, ml:Number):Void {
		depthQueueR = r;
		depthQueueG = g;
		depthQueueB = b;
		if (df != undefined) depthQueueFactor = df;
		maxLuminosity = ml;
	}

	public function setPhysicsProps (td:Number, tf:Number, tg:Number, te:Number):Void {
		if (td!=undefined) damp = td;
		if (tf!=undefined) friction = tf;
		if (tg!=undefined) gravity = tg;
		if (te!=undefined) elastic = te;
	}

	public function enable ():Void {
		_visible = isEnabled = true;
	}

	public function disable ():Void {
		_visible = isEnabled = false;
	}

	public function addSprite (mySprite:String, x:Number, y:Number, z:Number, wrap:Boolean):MovieClip {
		itemCount++;
		var m = attachMovie(mySprite, "m"+theDepth, theDepth, { x:x, y:y, z:z, wrapMode:wrap });
		theDepth--;
		if (theDepth==0) theDepth = maxDepth;
		return m;
	}

	public function addSpriteClass (mySprite:String, x:Number, y:Number, z:Number, wrap:Boolean, myClass:Function):MovieClip {
		var m = addSprite(mySprite, x, y, z, wrap);
		m.__proto__ = myClass.prototype;
		myClass.apply(m);
		m.onLoad();
		return m;
	}

	public function createSpriteClass (x:Number, y:Number, z:Number, wrap:Boolean, myClass:Function):MovieClip {
		itemCount++;
		var m = createEmptyMovieClip("m"+theDepth, theDepth);
		m.x = x;
		m.y = y;
		m.z = z;
		m.wrapMode = wrap;
		theDepth--;
		if (theDepth==0) theDepth = maxDepth;
		m.__proto__ = myClass.prototype;
		myClass.apply(m);
		m.onLoad();
		return m;
	}

	public function onEnterFrame () {

		// Manual camera control
		if (testing) {
			if (Key.isDown(Key.SHIFT)) {
				if (Key.isDown(Key.PGUP)) viewDist++;
				if (Key.isDown(Key.PGDN)) viewDist--;
			} else {
				var ts = 10;
				if (Key.isDown(Key.CONTROL)) ts = 1;
				if (Key.isDown(Key.RIGHT)) tx += ts;
				if (Key.isDown(Key.LEFT)) tx -= ts;
				if (Key.isDown(Key.UP)) ty -= ts;
				if (Key.isDown(Key.DOWN)) ty += ts;
				if (Key.isDown(Key.PGUP)) tz += ts;
				if (Key.isDown(Key.PGDN)) tz -= ts;
				if (Key.isDown(Key.SPACE)) trace("tx:"+tx+" ty:"+ty+" tz:"+tz+" viewDist:"+viewDist);
			}
		}

		// Mouse controls
		if (mouseControlled && ready) {
			tx -= _xmouse/10;
			tz += (_ymouse-35)/20;
			ty = vz*8; // camera lean
		}

		// Simulation paused?
		if (isPaused) return;

		// Camera movement
		vx += (tx-x-(_xmouse*mouseSwayX))/damp;
		vx *= friction;
		x += vx;

		vy += (ty-y-(_ymouse*mouseSwayY))/damp;
		vy *= friction;
		y += vy;

		vz += (tz-z)/damp;
		vz *= friction;
		z += vz;

		if (rounding!=0 && rounding!=undefined) {
			x = Math.round(x*rounding)/rounding;
			y = Math.round(y*rounding)/rounding;
			z = Math.round(z*rounding)/rounding;
		}

		// Render
		for (var i in this) {
			var m = this[i];
			m.update();

			var mx = m.x+x;
			var my = m.y+y;
			var mz = m.z+z;

			// Popup check
			if (mz>maxDist || mz<=0 || !m.isVisible) {
				m._visible = false;
				//m.filters = [];
				continue;
			} else {
				m._visible = true;
			}

			// Render
			var p = viewDist/mz;
			m._xscale = m._yscale = 100*p;
			m._x = mx*p;
			m._y = my*p;

			// Depth-queue tinting and lighting
			if (depthQueuing) {
				var d = (mz/maxDist);
				var ca = Math.min((100-(100*d))*depthQueueFactor, maxLuminosity);
				var co = 255*d*depthQueueFactor;
				var ct = { ra:ca+m.getSelfLighting("r"), ga:ca+m.getSelfLighting("g"), ba:ca+m.getSelfLighting("b"), rb:co*depthQueueR, gb:co*depthQueueG, bb:co*depthQueueB };
				m.c.setTransform(ct);
			}

			// Close-up fade
			if (fadeFactor && mz<(100/fadeFactor)+minDist) {
				m._alpha = Math.min((mz*fadeFactor)-minDist, 100);
				/*
				var bl = 4; //(100-m._alpha)/4;
				if (m.filters.length==0 && m._alpha<100 && m.spriteType=="dummy" && Math.abs(mx)<100) {
					m.filters = [new flash.filters.BlurFilter(bl,bl,1)];
				} else {
					m.filters = [];
				}
				*/
				if (m._alpha<minAlpha) {
					m._visible = false;
					//m.filters = [];
				}
			} else {
				//m.filters = [];
				m._alpha = 100;
			}

			// Sprite sounds
			if (Math.abs(vz)>mz && Math.abs(vz)>5 && Math.abs(mx)<100 && m.numSounds) _parent.soundManager.playSound("snd_flyby_"+m.spriteType+random(m.numSounds),0,0,Math.min((Math.abs(vz)*10)-Math.abs(mx),100),mx);

			// Z-sorting
			var zNudge = m.zNudge==undefined ? 0 : m.zNudge;
			var myDepth = (maxDist*zDepthScale)-Math.round(mz*zDepthScale)+zNudge;
			while (typeof(getInstanceAtDepth(myDepth))=="movieclip") myDepth--;
			m.swapDepths(myDepth);
		}
	}

}